
package yipdf;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;


class PDFJamWrapper {

    private IConsole console_;

    PDFJamWrapper(IConsole console) {
        console_ = console;
    }

    File runPdfJam(File file, boolean landscape, String extractOption, String trimOption, boolean imposition4up, float scale, boolean cleanup)
    throws IOException {
        String name = file.getName().substring(0, file.getName().length() - 4);
        List<String> cmd = new ArrayList<String>();
        File tmpIn=file, tmpOut=null;
        // step #1 : extract subset
        if (extractOption != null) {
            tmpOut = new File(tmpIn.getParentFile(), name + "-subset.pdf");
            cmd.clear();
            cmd.add("pdfjam");
            if (landscape) {
                cmd.add("--landscape");
            }
            cmd.add(tmpIn.getAbsolutePath());
            cmd.add(extractOption);
            cmd.add("-o");
            cmd.add(tmpOut.getAbsolutePath());
            runCommand("Extract", cmd);
            if (cleanup && tmpIn!=file) tmpIn.delete();
            tmpIn=tmpOut;
        }
        // step #2 : trim
        if (trimOption != null) {
            tmpOut = new File(tmpIn.getParentFile(), name + "-trim.pdf");
            cmd.clear();
            cmd.add("pdfjam");
            if (landscape) {
                cmd.add("--landscape");
            }
            cmd.add("--trim");
            cmd.add(trimOption);
            cmd.add("--clip");
            cmd.add("true");
            cmd.add(tmpIn.getAbsolutePath());
            cmd.add("-o");
            cmd.add(tmpOut.getAbsolutePath());
            runCommand("Trim", cmd);
            if (cleanup && tmpIn!=file) tmpIn.delete();
            tmpIn=tmpOut;
        }
        // step #3 : imposition (4up)
        if (imposition4up) {
            tmpOut = new File(tmpIn.getParentFile(), name + "-4up-tmp.pdf");
            cmd.clear();
            cmd.add("pdfjam");
            cmd.add("--nup");
            cmd.add("--1x2");
            cmd.add(tmpIn.getAbsolutePath());
            cmd.add("-o");
            cmd.add(tmpOut.getAbsolutePath());
            runCommand("4up#1", cmd);
            if (cleanup && tmpIn!=file) tmpIn.delete();
            tmpIn=tmpOut;
            tmpOut = new File(tmpIn.getParentFile(), name + "-4up.pdf");
            cmd.clear();
            cmd.add("pdfjam");
            if (landscape) {
                cmd.add("--landscape");
            }
            cmd.add("--nup");
            cmd.add("--2x1");
            if (scale>0.0F) {
                cmd.add("--scale");
                cmd.add(String.valueOf(scale));
            }
            cmd.add(tmpIn.getAbsolutePath());
            cmd.add("-o");
            cmd.add(tmpOut.getAbsolutePath());
            runCommand("4up#2", cmd);
            if (cleanup && tmpIn!=file) tmpIn.delete();
            tmpIn=tmpOut;
        }
        return tmpOut;
    }
    

    private void runCommand(String step, List<String> cmd) throws IOException {
        try {
            ProcessBuilder builder = new ProcessBuilder(cmd);
            Process p = builder.start();
            CountDownLatch latch = startPrinterThreads(p, "PDFTrim");
            p.waitFor();
        } catch (InterruptedException ex) {
            String message = "Interrupted at step '"+step+"'";
            Logger.getLogger(YiPDF.class.getName()).log(Level.SEVERE, message, ex);
            throw new IOException(message);
        } catch (IOException ex) {
            String message = "I/O error at step '"+step+"'";
            Logger.getLogger(YiPDF.class.getName()).log(Level.SEVERE, message, ex);
            throw new IOException(message);
        }
    }


    /*
     * Start 2 threads to process output streams.
     * Return a latch that is "released" when both threads have finished their work.
     */
    private CountDownLatch startPrinterThreads(final Process process, final String cmd) {

        final CountDownLatch latch = new CountDownLatch(2); // 2 tasks need to be finished before release

        Runnable stdErrDumper = new Runnable() {

            public void run() {
                InputStream is = process.getErrorStream();
                LineNumberReader r = new LineNumberReader(new InputStreamReader(is));
                String line = "";
                try {
                    while (line != null) {
                        line = r.readLine();
                        if (line != null) {
                            console_.printError(line);
                        }
                    }
                } catch (IOException e) {
                    Logger.getLogger(YiPDF.class.getName()).log(Level.SEVERE, "Failed to read error output stream for " + cmd, e);
                } finally {
                    try {
                        r.close();
                    } catch (IOException e) {
                        Logger.getLogger(YiPDF.class.getName()).log(Level.SEVERE, "Failed to close process stderr", e);
                    }
                }
                latch.countDown();
            }
        };

        Runnable stdOutDumper = new Runnable() {

            public void run() {
                InputStream is = process.getInputStream();
                LineNumberReader r = new LineNumberReader(new InputStreamReader(is));
                String line = "";
                try {
                    while (line != null) {
                        line = r.readLine();
                        if (line != null) {
                            console_.print(line);
                        }
                    }
                    console_.printSpecial("Exited: " + cmd);
                } catch (IOException e) {
                    Logger.getLogger(YiPDF.class.getName()).log(Level.SEVERE, "Failed to read standard output stream for " + cmd, e);
                } finally {
                    try {
                        r.close();
                    } catch (IOException e) {
                        Logger.getLogger(YiPDF.class.getName()).log(Level.SEVERE, "Failed to close process stdout", e);
                    }
                }
                latch.countDown();
            }
        };

        Thread thread = new Thread(stdErrDumper, "stderrDumper");
        thread.start();
        thread = new Thread(stdOutDumper, "stdoutDumper");
        thread.start();
        return latch;
    }

}
